//
//  shader.cpp
//  RenderV1
//
//  Created by 黄翔 on 2019/8/20.
//  Copyright © 2019 黄翔. All rights reserved.
//

#include <glad/glad.h>
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>

#include "Shader.h"
#include "render_v1.h"

using namespace std;

hxe::Shader::Shader(const string &vertexPath, const string &fragmentPath)
{
    std::string vertexCode;
    std::string fragmentCode;
    std::ifstream vShaderFile;
    std::ifstream fShaderFile;

    vShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
    fShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
    try
    {
        vShaderFile.open(vertexPath);
        fShaderFile.open(fragmentPath);
        std::stringstream vShaderStream, fShaderStream;

        vShaderStream << vShaderFile.rdbuf();
        fShaderStream << fShaderFile.rdbuf();

        vShaderFile.close();
        fShaderFile.close();

        vertexCode = vShaderStream.str();
        fragmentCode = fShaderStream.str();
    }
    catch (std::ifstream::failure e)
    {
        LoggerInst->print("loadShader failed");
    }
    const char* vShaderCode = vertexCode.c_str();
    const char * fShaderCode = fragmentCode.c_str();

    unsigned int vertex, fragment;

    vertex = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertex, 1, &vShaderCode, NULL);
    glCompileShader(vertex);
    checkCompileErrors(vertex, "VERTEX");

    fragment = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragment, 1, &fShaderCode, NULL);
    glCompileShader(fragment);
    checkCompileErrors(fragment, "FRAGMENT");

    this->id = glCreateProgram();
    glAttachShader(this->id, vertex);
    glAttachShader(this->id, fragment);
    glLinkProgram(this->id);
    checkCompileErrors(this->id, "PROGRAM");

    glDeleteShader(vertex);
    glDeleteShader(fragment);
}


hxe::Shader::~Shader()
{
    glDeleteProgram(id);
}

void hxe::Shader::use()
{
    glUseProgram(this->id);
}

void hxe::Shader::setBool(const std::string &name, bool value) const
{
    glUniform1i(glGetUniformLocation(this->id, name.c_str()), (int)value);
}

void hxe::Shader::setInt(const std::string &name, int value) const
{
    glUniform1i(glGetUniformLocation(this->id, name.c_str()), value);
}

void hxe::Shader::setFloat(const std::string &name, float value) const
{
    glUniform1f(glGetUniformLocation(this->id, name.c_str()), value);
}

void hxe::Shader::setVector4(const std::string &name, float x, float y, float z, float w) const
{
    glUniform4f(glGetUniformLocation(this->id, name.c_str()), x, y, z, w);
}

void hxe::Shader::setVector4(const std::string &name, const Vector4& v) const
{
    glUniform4f(glGetUniformLocation(this->id, name.c_str()), v.x, v.y, v.z, v.w);
}

void hxe::Shader::setVector3(const std::string &name, float x, float y, float z) const
{
    glUniform3f(glGetUniformLocation(this->id, name.c_str()), x, y, z);
}

void hxe::Shader::setVector3(const std::string &name, const Vector3& v) const
{
    glUniform3f(glGetUniformLocation(this->id, name.c_str()), v.x, v.y, v.z);
}

void hxe::Shader::setMatrix(const std::string &name, const Matrix4x4 &matrix) const
{
    glUniformMatrix4fv(glGetUniformLocation(this->id, name.c_str()), 1, GL_FALSE, glm::value_ptr(matrix));
}

void hxe::Shader::checkCompileErrors(unsigned int shader, std::string type)
{
    int success;
    char infoLog[1024];
    if (type != "PROGRAM")
    {
        glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
        if (!success)
        {
            glGetShaderInfoLog(shader, 1024, NULL, infoLog);
            std::string logStr = "shader compilatiion error:";
            logStr += infoLog;
            LoggerInst->print(logStr.c_str());
        }
    }
    else
    {
        glGetProgramiv(shader, GL_LINK_STATUS, &success);
        if (!success)
        {
            glGetProgramInfoLog(shader, 1024, NULL, infoLog);
            std::string logStr = "shader link error:";
            logStr += infoLog;
            LoggerInst->print(logStr.c_str());
        }
    }
}
